Há algumas semanas, criei o meu primeiro formulário, que pode ser acessado aqui. Ele foi feito por pura curiosidade, sendo sobre um tópico consideravelmente não sério: como as pessoas acham que se dariam em uma luta contra animais. Decidi utilizá-lo como base para o meu trabalho final de Visualização da Informação, pois aproveitaria a oportunidade para aprender a criar minhas própria bases de dados. Dessa forma, além de tirar conclusões sobre o tópico, poderia tirar conclusões de forma meta, sobre a natureza de bases de dados.
O questionário consiste de 75 perguntas, sendo estas:
De cara, percebi algumas melhorias que poderia ter feito. A primeira é o número de animais, um tanto elevado, que faz com que as pessoas fiquem cansadas durante a realização do formulário. A segunda é que poderiam ser inclusas mais perguntas sobre o usuário, tal como peso e altura, personalidade, etc.
De qualquer forma, o formulário foi feito e, como esperado, as pessoas responderam de forma bem divertida. No total, obtive 101 respostas. A seguir, vou analisar os dados obtidos. Primeiro, vamos importar as bibliotecas necessárias:
import pandas as pd
import plotly as py
import plotly.graph_objs as go
import plotly.express as px
Como utilizei do Google Forms, fui capaz de extrair um csv com todos os dados. Posso utilizar da biblioteca pandas para lê-lo.
# reading the data
df = pd.read_csv('respostas.csv')
df
| Carimbo de data/hora | Primeiramente, conte-me um tico sobre você. Quantos anos você tem? | Entendi. E como você se identifica? | Ah, que bacana. Por obséquio, caso estude na FGV Rio, marque as caixas correspondentes à(s) sua(s) graduação(/ões): | Ok, vamos começar. As regras são o seguinte. Você e o animal numa luta mano a mano. O animal a ser imaginado é um representante médio de sua espécie (pode se basear na foto).\nNem você nem o animal tem direito a usar armas e a luta se passa num local plano e infinito, de forma que não é possível tomar vantagens do cenário. Ambos estão extremamente motivados, e apenas um sairá vivo. A resposta é dada de 0 a 10, representando o quão confiante você está que derrotaria o animal caso necessário. Capiche? | Ok, primeiro oponente. Você venceria um porco médio? | [Porco] Gostaria de esclarecer um pouco mais? Sinta-se vontade para contar alguma estratégia ou motivos pelo qual acha que perderia/venceria. | Pagarias o pato? | [Pato] Gostaria de esclarecer um pouco mais? Sinta-se vontade para contar alguma estratégia ou motivos pelo qual acha que perderia/venceria. | Hmm.. e quanto a um macaco? | ... | [Tartaruga] Gostaria de esclarecer um pouco mais? Sinta-se vontade para contar alguma estratégia ou motivos pelo qual acha que perderia/venceria. | Um camelo? (tem duas corcovas) | [Camelo] Gostaria de esclarecer um pouco mais? Sinta-se vontade para contar alguma estratégia ou motivos pelo qual acha que perderia/venceria. | E um dromedário? (tem uma corcova) | [Dromedário] Gostaria de esclarecer um pouco mais? Sinta-se vontade para contar alguma estratégia ou motivos pelo qual acha que perderia/venceria. | E um lagarto? (não tem corcovas) | [Lagarto] Gostaria de esclarecer um pouco mais? Sinta-se vontade para contar alguma estratégia ou motivos pelo qual acha que perderia/venceria. | Por fim, vencerias um ornitorrinco? (ambos tem direito a um chapéu) | [Ornitorrinco] Gostaria de esclarecer um pouco mais? Sinta-se vontade para contar alguma estratégia ou motivos pelo qual acha que perderia/venceria. | Obrigado por chegar ao fim! Alguma sugestão? | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 30/11/2022 00:13:12 | 21 | Masculino | NaN | Sim. Li o texto inteiro (umas duas vezes pra t... | 8 | Porcos são pesados (fortes) porem gordos e len... | 10 | Patos voam e podem me atacar sem eu poder revi... | 8 | ... | Bem resiliente mas eu acertaria os pontos fracos | 8 | Intimidante porém eu não o considero uma ameaça | 9 | Menos intimidante ainda | 5 | Baseado na imagem esse é um dragão de komodo e... | 9 | 10 se for fêmea, 9 se for macho. É só não toca... | NaN |
| 1 | 30/11/2022 00:21:07 | 21 | Masculino | Ciência de Dados | Sim. Li o texto inteiro (umas duas vezes pra t... | 10 | Animal tranquilo. Tem como cegar ele e é um ta... | 10 | Suave | 10 | ... | Teria que acertar na cabeça. Mas ela deixaria ... | 10 | Animal inofensivo. É um cavalo mais lento e me... | 10 | É um camelo que deu errado. Fácil. | 10 | Conseguiria também. Animal pequeno e quase ino... | 10 | Sim! Mas só na terra. Na água é o terreno dele | NaN |
| 2 | 30/11/2022 00:26:12 | 21 | Feminino | NaN | Sim. Li o texto inteiro (umas duas vezes pra t... | 3 | Porco tem a vantagem de peso e tamanho | 6 | Patos tem dentes e costumam ser bem agressivos. | 2 | ... | NaN | 2 | NaN | 2 | NaN | 0 | NaN | 0 | NaN | NaN |
| 3 | 30/11/2022 00:36:24 | 20 | Feminino | Ciência de Dados | Sim. Li o texto inteiro (umas duas vezes pra t... | 2 | Acho que ele me ganha fácil. Se eu caio no chã... | 8 | NaN | 1 | ... | Sem querer ser sanguinária. Mas ela nao conseg... | 5 | NaN | 5 | NaN | 3 | NaN | 10 | NaN | NaN |
| 4 | 30/11/2022 00:42:38 | 20 | Masculino | Ciência de Dados | Sim. Li o texto inteiro (umas duas vezes pra t... | 7 | Dado que é uma situação de vida ou morte e só ... | 10 | O papo pateta pintou o caneco\nSurrou a galinh... | 8 | ... | Ia chegar na luta pique super Mario. Ia ser só... | 3 | Tentaria acertar a cabeça dele e machucar as p... | 3 | Ele parece mais tranquilo que o camelo. Mas a ... | 9 | Vi muito homem-aranha e na minha vida. Acho qu... | 4 | Se ele souber artes marciais e se chamar Perry... | Que questionário enorme pqp |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 96 | 08/12/2022 20:09:03 | 19 | Feminino | NaN | Sim. Li o texto inteiro (umas duas vezes pra t... | 10 | Não é rápido e aparenta ser sonso | 10 | Animal de pequeno porte | 3 | ... | Animal fraco | 6 | NaN | 6 | NaN | 4 | NaN | 0 | NaN | NaN |
| 97 | 08/12/2022 22:44:43 | 22 | Masculino | Ciência de Dados | Sim. Li o texto inteiro (umas duas vezes pra t... | 10 | NaN | 10 | NaN | 10 | ... | NaN | 3 | NaN | 3 | NaN | 10 | NaN | 10 | NaN | NaN |
| 98 | 09/12/2022 21:16:02 | 21 | Masculino | NaN | Sim. Li o texto inteiro (umas duas vezes pra t... | 10 | Eu venceria por ser carnivoro e muito astuto | 10 | Ele poderia voar, porém a sou um animal pacien... | 10 | ... | meu mano jhonatan, a tartaruga de 150 anos iri... | 10 | era so driblar estilo neymar | 10 | q nem o cabelo so que nerfado | 0 | esse dragão somente são jorge | 0 | contra o perry não tem como, agora um normal n... | sim, coloque opções de justificativas para ven... |
| 99 | 09/12/2022 21:16:12 | 21 | Masculino | NaN | Sim. Li o texto inteiro (umas duas vezes pra t... | 3 | NaN | 6 | tentaria pega o pescoço do pato | 3 | ... | NaN | 0 | NaN | 0 | NaN | 0 | NaN | 0 | NaN | nada seria pario para o esqueleto negão com se... |
| 100 | 09/12/2022 21:34:24 | 19 | Masculino | NaN | Sim. Li o texto inteiro (umas duas vezes pra t... | 8 | eu derrubo o porco usando meu peso e um porco ... | 0 | terei a honra de dar a minha vida para a sobre... | 10 | ... | cara ele é o mestre do mestre do po do kung-fu... | 0 | ele da dano a distância enton no | 5 | metade da munição de cima então | 0 | OLHA AS GARRAS DESSE ARROMBADO | 5 | Se ele for o perry nem fudendo | nha |
101 rows × 76 columns
Para trabalharmos com a tabela, vamos torná-la mais conveniente. Vamos trabalhar apenas com as cem primeiras respostas, para facilitar as conclusões.
df = df.iloc[:100]
Vamos remover as colunas que não serão utilizadas. Para isso, antes devemos arrumar o nome das colunas, para facilitar a manipulação.
df.columns = ['Hora', 'Idade', 'Genero', 'FGV', 'Regras', 'Porco', 'Porco2', 'Pato', 'Pato2', 'Macaco', 'Macaco2', 'Galinha', 'Galinha2',
'Lobo', 'Lobo2', 'Cervo', 'Cervo2', 'Crocodilo', 'Crocodilo2', 'Ganso', 'Ganso2', 'Gorila', 'Gorila2', 'Avestruz', 'Avestruz2',
'Javali', 'Javali2', 'Bode', 'Bode2', 'Cavalo', 'Cavalo2', 'Coruja', 'Coruja2', 'Onca', 'Onca2', 'Guaxinim', 'Guaxinim2',
'Cobra', 'Cobra2', 'Vaca', 'Vaca2', 'Canguru', 'Canguru2', 'Leao', 'Leao2', 'Caranguejo', 'Caranguejo2', 'Morsa', 'Morsa2',
'Touro', 'Touro2', 'Urso', 'Urso2', 'Raposa', 'Raposa2', 'Pinguim', 'Pinguim2', 'Rato', 'Rato2', 'Capivara', 'Capivara2',
'Elefante', 'Elefante2', 'Porcoespinho', 'Porcoespinho2', 'Tartaruga', 'Tartaruga2', 'Camelo', 'Camelo2',
'Dromedario', 'Dromedario2', 'Lagarto', 'Lagarto2', 'Ornitorrinco', 'Ornitorrinco2', 'Sugestao']
df = df.drop(columns=['Regras','Sugestao'])
Como a maioria das respostas tinham limites definidos e eram obrigatórias, não há necessidade de tratar os dados faltantes. Contudo, houve uma brecha na pergunta referente à idade, que permitia que o usuário inserisse um valor não inteiro. Dessa forma, 2% dos dados devem ser tratados:
df.loc[48, 'Idade'] = int(19)
df.loc[53, 'Idade'] = int(19)
Agora, podemos calcular informações sobre os dados, tal como a idade média. Precisamos converter a coluna de idade para inteiro, pois ela está como string, devido à forma que os inputs foram salvos.
# convert column to int
df['Idade'] = df['Idade'].astype(int)
# mean age
df['Idade'].mean()
22.01
Vamos agora remover as colunas que não serão utilizadas. Vamos criar outro dataframe, para caso seja necessário voltar atrás.
df2 = df[['Idade', 'Genero', 'Porco', 'Pato', 'Macaco', 'Galinha',
'Lobo', 'Cervo', 'Crocodilo', 'Ganso', 'Gorila', 'Avestruz',
'Javali', 'Bode', 'Cavalo', 'Coruja', 'Onca', 'Guaxinim',
'Cobra', 'Vaca', 'Canguru', 'Leao', 'Caranguejo', 'Morsa',
'Touro', 'Urso', 'Raposa', 'Pinguim', 'Rato', 'Capivara',
'Elefante', 'Porcoespinho', 'Tartaruga', 'Camelo',
'Dromedario', 'Lagarto', 'Ornitorrinco']]
Agora, com a ajuda da biblioteca plotly, podemos visualizar os dados de forma interativa. A primeira ideia foi criar um gráfico de barras com a porcentagem média de confiança para cada animal.
Para destacar aqueles de maior confiança, os valores foram ordenados de forma decrescente. Também foi pintado de verde as barras cuja confiança média era maior que 50%.
# colors
colors = ['green'] * (len(df2.columns) -2)
# paint red if the value is below 50%
for i in range(0, len(df2.columns)-2):
if df2.drop(columns=['Idade', 'Genero']).mean()[i] < 5:
colors[i] = 'crimson'
# plot
fig = go.Figure(data=[go.Bar(
x=df2.drop(columns=['Idade', 'Genero']).columns,
y=df2.drop(columns=['Idade', 'Genero']).mean(),
marker_color=colors # marker color can be a single color value or an iterable
)])
# title
fig.update_layout(title_text='Porcentagem média de confiança')
# axis titles
fig.update_xaxes(title_text='Animais')
fig.update_yaxes(title_text='Média')
# order the bars
fig.update_xaxes(categoryorder='total descending')
# rotate the x axis labels
fig.update_xaxes(tickangle=45)
# show
fig.show(renderer='notebook')
# mean of all animals
df2.drop(columns=['Idade']).mean().mean()
C:\Users\RODRIG~1\AppData\Local\Temp/ipykernel_29692/2717217253.py:2: FutureWarning: Dropping of nuisance columns in DataFrame reductions (with 'numeric_only=None') is deprecated; in a future version this will raise TypeError. Select only valid columns before calling the reduction.
4.130874458874459
Podemos ver que apenas 14 dos 35 animais possuem uma confiança média maior que 50%. Além disso, a confiança média para os animais é de 41,3%. O animal pelo qual as pessoas se demonstram mais confiantes é a galinha, com 85,8%. Enquanto isso, o animal pelo qual as pessoas se demonstram menos confiantes é o gorila, com 5,2%.
Outra informação do formulário que podemos utilizar é o gênero. Podemos agrupar os dados por gênero e realizar o mesmo gráfico anterior.
Para isso, vamos criar um dataframe adicional, df3, que é agrupado por gênero. Como cada gênero terá sua própria cor, decidi plotar uma linha horizontal para simbolizar a confiança de 50%.
# gender list
gender = ['Feminino', 'Masculino', 'Outro', 'Prefiro não dizer']
# color list
color = ['#FA7A9C', '#ACBEFA', '#7AFA8B', '#FADB87']
# df3 creation
df3 = df2.drop(columns=['Idade'])
df3 = df3.groupby(['Genero'])
# all the bars
bars = []
bars.append(go.Bar(
x=df2.drop(columns=['Idade']).mean().index,
y=df2.drop(columns=['Idade']).mean(),
name='Total',
marker_color='#FAB587'
))
for i in range(0, 4):
bars.append(go.Bar(
x=df3.get_group(gender[i]).mean().index,
y=df3.get_group(gender[i]).mean(),
name= gender[i],
marker_color=color[i]))
# plot
fig = go.Figure(data=bars)
# add a horizontal line at y=5
fig.add_shape(type="line", x0=0, y0=5, x1=34, y1=5, line=dict(color="Black", width=1))
# title
fig.update_layout(title_text='Média de confiança por gênero')
# axis titles
fig.update_xaxes(title_text='Animais')
fig.update_yaxes(title_text='Média')
# order the bars
fig.update_xaxes(categoryorder='total descending')
# group the bars
fig.update_layout(barmode='group')
# rotate x axis
fig.update_xaxes(tickangle=45)
# show
fig.show(renderer='notebook')
C:\Users\RODRIG~1\AppData\Local\Temp/ipykernel_29692/3443862457.py:11: FutureWarning: Dropping of nuisance columns in DataFrame reductions (with 'numeric_only=None') is deprecated; in a future version this will raise TypeError. Select only valid columns before calling the reduction. C:\Users\RODRIG~1\AppData\Local\Temp/ipykernel_29692/3443862457.py:12: FutureWarning: Dropping of nuisance columns in DataFrame reductions (with 'numeric_only=None') is deprecated; in a future version this will raise TypeError. Select only valid columns before calling the reduction. C:\Users\RODRIG~1\AppData\Local\Temp/ipykernel_29692/3443862457.py:18: FutureWarning: Dropping of nuisance columns in DataFrame reductions (with 'numeric_only=None') is deprecated; in a future version this will raise TypeError. Select only valid columns before calling the reduction. C:\Users\RODRIG~1\AppData\Local\Temp/ipykernel_29692/3443862457.py:19: FutureWarning: Dropping of nuisance columns in DataFrame reductions (with 'numeric_only=None') is deprecated; in a future version this will raise TypeError. Select only valid columns before calling the reduction.
Incluindo a média total, tal como as quatro opções de resposta para gênero, o gráfico fica um tanto quanto poluído. Contudo, uma vez que estamos lidando com a biblioteca plotly, podemos utilizar o recurso de habilitar e desabilitar as categorias, para facilitar a visualização.
print('Média de confiança por gênero:')
print('Feminino: ', df3.get_group('Feminino').mean().mean())
print('Masculino: ', df3.get_group('Masculino').mean().mean())
print('Outro: ', df3.get_group('Outro').mean().mean())
print('Prefiro não dizer: ', df3.get_group('Prefiro não dizer').mean().mean())
Média de confiança por gênero: Feminino: 3.4334975369458136 Masculino: 4.599350305960566 Outro: 3.1904761904761902 Prefiro não dizer: 3.076190476190476
C:\Users\RODRIG~1\AppData\Local\Temp/ipykernel_29692/1306151253.py:2: FutureWarning: Dropping of nuisance columns in DataFrame reductions (with 'numeric_only=None') is deprecated; in a future version this will raise TypeError. Select only valid columns before calling the reduction. C:\Users\RODRIG~1\AppData\Local\Temp/ipykernel_29692/1306151253.py:3: FutureWarning: Dropping of nuisance columns in DataFrame reductions (with 'numeric_only=None') is deprecated; in a future version this will raise TypeError. Select only valid columns before calling the reduction. C:\Users\RODRIG~1\AppData\Local\Temp/ipykernel_29692/1306151253.py:4: FutureWarning: Dropping of nuisance columns in DataFrame reductions (with 'numeric_only=None') is deprecated; in a future version this will raise TypeError. Select only valid columns before calling the reduction. C:\Users\RODRIG~1\AppData\Local\Temp/ipykernel_29692/1306151253.py:5: FutureWarning: Dropping of nuisance columns in DataFrame reductions (with 'numeric_only=None') is deprecated; in a future version this will raise TypeError. Select only valid columns before calling the reduction.
A opção "Outro" e "Prefiro não dizer" obtiveram respectivamente 6 e 3 respostas. Como a quantidade de respostas é muito pequena, é possível que essa amostra não seja representativa. Dessa forma, vamos comparar apenas os gêneros "Masculino" e "Feminino".
Percebemos que, com exceção do ornitorrinco, os homens apresentam uma confiança consideravelmente maior que as mulheres.
Em seguida, gostaria de responder uma pergunta meta: a que ponto a quantidade de respostas pode se tornar representativa? Percebi que, no começo, havia bastante respostas contrárias, mas que, com o passar do tempo, a média foi se estabilizando.
Para isso, decidi criar outro gráfico de barras, que representa a comparação entre a média final e a média acumulada.
# df4 creation
df4 = df2.drop(columns=['Idade', 'Genero'])
# plot
fig = go.Figure(data=[go.Bar(
x=df4.mean().index,
y=df4.mean(),
name='Total',
marker_color='#FAB587'
), go.Bar(
x=df4[:0].mean().index,
y=df4[:0].mean(),
name='Acumulado',
marker_color='#3BA7AD'
)],
# play and pause buttons
layout = go.Layout(
updatemenus=[dict(
type="buttons",
buttons=list([dict(label="Play",
method="animate",
args=[None]),
dict(label="Pause",
method="animate",
args=[[None], dict(frame=dict(duration=0, redraw=False), transition=dict(duration=0), fromcurrent=True, mode="immediate")])
]))]
),
# animation frames
frames = [go.Frame(data=[go.Bar(x=df4.mean().index, y=df4.mean()),
go.Bar(x=df4[:i].mean().index, y=df4[:i].mean(),
# print the number of responses
text=[str(i) for i in df4[:i].count()],
textposition='outside', textfont=dict(color='Black', size=12, family='Arial, sans-serif'))
]) for i in range(0, len(df4.index)+1)])
# add a horizontal line at y=50
fig.add_shape(type="line", x0=0, y0=5, x1=34, y1=5, line=dict(color="Black", width=1))
# order x axis by total
fig.update_xaxes(categoryorder='total descending')
# add title
fig.update_layout(title_text='Média por número de respostas')
# add axis titles
fig.update_xaxes(title_text='Animais')
fig.update_yaxes(title_text='Média')
# group the bars
fig.update_layout(barmode='group')
# rotate x axis labels
fig.update_xaxes(tickangle=45)
# show
fig.show(renderer='notebook')
Para esse gráfico, foi colocado um botão de play e pause, para que o usuário possa acompanhar a evolução das média, a partir do número de respostas. Podemos perceber que no começo a média acumulada é bem maior que a média final, uma vez que os extremos são mais comuns. Contudo, com o passar do tempo, a média final se aproxima da média acumulada.
É perceptível também que não é necessário um valor muito alto de respostas para que a média final se aproxime da média acumulada. A partir de 30 respostas, a média final já se aproxima da média acumulada.
Para que possamos ter uma ideia melhor, vamos calcular a diferença e plotar um gráfico de linha.
diferences = []
for i in range(0, len(df4.index)):
diferences.append(df4.mean() - df4[:i].mean())
# transform list into dataframe
diff = pd.DataFrame(diferences)
# remove first row
diff = diff.drop(diff.index[0])
# plot line
fig = px.line(diff, x=diff.index, y=diff.columns)
# add a horizontal line at y=0
fig.add_shape(type="line", x0=0, y0=0, x1=100, y1=0, line=dict(color="Black", width=1))
# add title
fig.update_layout(title_text='Diferença entre média final e média acumulada')
# add axis titles
fig.update_xaxes(title_text='Número de respostas')
fig.update_yaxes(title_text='Diferença')
# change name of legend
fig.update_layout(legend_title_text='Animais')
# show
fig.show(renderer='notebook')
Podemos perceber que não são necessárias tantas respostas para conseguir uma amostra representativa. Contudo, ainda devemos tomar cuidado, tal como no caso do gênero.
Sabendo disso, podemos fazer uma última comparação com gráfico de barras, comparando as médias totais com as médias de alunos da FGV.
# only rows with non null values for the 'FGV' column
dfgv = df[df['FGV'].notnull()]
# value count for gender
dfgv['Genero'].value_counts()
dfgv = dfgv[['Porco', 'Pato', 'Macaco', 'Galinha',
'Lobo', 'Cervo', 'Crocodilo', 'Ganso', 'Gorila', 'Avestruz',
'Javali', 'Bode', 'Cavalo', 'Coruja', 'Onca', 'Guaxinim',
'Cobra', 'Vaca', 'Canguru', 'Leao', 'Caranguejo', 'Morsa',
'Touro', 'Urso', 'Raposa', 'Pinguim', 'Rato', 'Capivara',
'Elefante', 'Porcoespinho', 'Tartaruga', 'Camelo',
'Dromedario', 'Lagarto', 'Ornitorrinco']]
# plot
fig = go.Figure(data=[go.Bar(
x=df4.mean().index,
y=df4.mean(),
name='Total',
marker_color='#FAB587'
), go.Bar(
x=dfgv.mean().index,
y=dfgv.mean(),
name='Alunos FGV',
marker_color='#AC9AD2'
)])
# add a horizontal line at y=50
fig.add_shape(type="line", x0=0, y0=5, x1=34, y1=5, line=dict(color="Black", width=1))
# order x axis by total
fig.update_xaxes(categoryorder='total descending')
# add title
fig.update_layout(title_text='Média por número de respostas')
# add axis titles
fig.update_xaxes(title_text='Animais')
fig.update_yaxes(title_text='Média')
# group the bars
fig.update_layout(barmode='group')
# rotate x axis labels
fig.update_xaxes(tickangle=45)
# show
fig.show(renderer='notebook')
print("Média de confiança geral:", df4.mean().mean())
print("Média de confiança alunos FGV:", dfgv.mean().mean())
Média de confiança geral: 4.130874458874459 Média de confiança alunos FGV: 4.518406593406593
Percebemos que os alunos da FGV são mais confiantes que a média geral, sendo essa bem próxima da média masculina. Isso não é tão surpreendente, caso vejamos quais os gêneros predominantes nas respostas:
df[df['FGV'].notnull()]['Genero'].value_counts()
Masculino 33 Feminino 5 Prefiro não dizer 2 Name: Genero, dtype: int64
Por fim, gostaria de realizar uma análise da média de confiança por idade. Para isso, achei conveniente utilizar o boxplot.
# plot
fig = px.box(df2.sort_values(by=['Idade']), x='Idade', y= df2.drop(columns=['Idade', 'Genero']).columns)
# changes the scale of x axis
fig.update_xaxes(type='category')
# add title
fig.update_layout(title_text='Distribuição de confiança por idade')
# add axis titles
fig.update_xaxes(title_text='Idade')
fig.update_yaxes(title_text='Confiança')
# show
fig.show(renderer='notebook')
Esse gráfico leva em considerações quais respostas foram mais comuns, e não apenas a média. Por isso, todos os boxplots estão com a mesma escala. Infelizmente, não foi possível identificar um padrão, uma vez que a maioria das respostas foram de pessoas com idade entre 18 e 22 anos. Isso pode ser explicado pelo fato de que a maioria dos alunos da FGV se encontra nessa faixa etária, sendo esse o meio que mais consegui divulgar o formulário. Em próximas experiências, seria interessante tentar aumentar a quantidade de respostas de pessoas com idades mais avançadas. A escassez dessas respostas pode ser visualizada melhor se o boxplot for referente à média da confiança:
df5 = df2.sort_values(by=['Idade'])
# plot
fig = px.box(df5, x='Idade', y= df5[df5.drop(columns=['Idade', 'Genero']).columns].mean(axis=1))
# changes the scale of x axis
fig.update_xaxes(type='category')
# add title
fig.update_layout(title_text='Média de confiança por idade')
# add axis titles
fig.update_xaxes(title_text='Idade')
fig.update_yaxes(title_text='Média')
# show
fig.show(renderer='notebook')
Podemos perceber que a metade das idades só apresenta uma resposta, o que pode ser um indicativo de que a amostra não é representativa.
Contudo, é possível que essa amostra seja representativa para a faixa etária de 18 a 22 anos, uma vez que a maioria das respostas foi de pessoas nessa faixa etária.
Por mais que as respostas detalhadas tenham sido engraçadas, infelizmente não podemos tirar proveito, uma vez que as palavras mais comuns são artigos e pronomes.
Por exemplo, observemos o ornitorrinco, o único animal sobre qual respostas do gênero feminino apresentaram uma confiança maior:
# check most repeated words
df['Ornitorrinco2'].str.lower().str.split(expand=True).stack().value_counts()
o 17
ele 14
não 12
eu 12
um 11
..
depende 1
ou 1
fêmea 1
(macho: 1
problemas 1
Length: 265, dtype: int64
Para um futuro formulário, talvez seja recomendável que as respostas sejam mais objetivas, tal como um conjunto de palavras-chave.